#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <sys/ioctl.h>
#include <poll.h>

#include "./include/common.h"

#define BUFSIZE   32 /* 设置最大偏移量为64, 方便打印完整的内存空间数据*/

#define CMD_TEST0 _IO('S', 0)
#define CMD_TEST1 _IOW('S', 1, int)
#define CMD_TEST2 _IOR('S', 2, int)
#define CMD_TEST3 _IOW('S', 3, int)

struct __CMD_TEST{
	int a;
	int b;
	int c;
};

/**
 * @brief  usage_info()
 * @note   打印帮助信息
 * @param [in]
 * @param [out]
 * @retval 
 */
void usage_info(void)
{
	printf("\n");
	printf("+++++++++++++++++++++++++++++++++++++++++\n");
	printf("+       help information  @sumu          +\n");
	printf("+++++++++++++++++++++++++++++++++++++++++\n");

	printf("help:\n");
	printf("use format: ./app_name /dev/device_name arg1 ... \n");
	printf("            ./app_demo.out /dev/sdevice 1 x data # 从x位置读取 \n");
	printf("            ./app_demo.out /dev/sdevice 2 x data # 从x位置写入 \n");
	printf("            ./app_demo.out /dev/sdevice 3 x data # x为任意值 \n");
    printf("            驱动中buf最大为32字节 \n");
	printf("\n");

	printf("command info:\n");
	printf("  (1)load module  : insmod module_name.ko\n");
	printf("  (2)unload module: rmmod module_name.ko\n");
	printf("  (3)show module  : lsmod\n");
	printf("  (4)view device  : cat /proc/devices\n");
	printf("  (5)create device node: mknod /dev/device_name c major_num secondary_num \n");
	printf("  (6)show device node  : ls /dev/device_name \n");
	printf("  (7)show device vlass  : ls /sys/class \n");
	printf("+++++++++++++++++++++++++++++++++++++++++\n");
}

void arg_info_print(int argc, char *argv[])
{
    int i = 0;
    PRT("total arg is %d: ", argc);
    for (i = 0; i < argc; i++) 
    {
        printf("argv[%d]=%s ", i, argv[i]);
    }
    printf("\n");
}

int main(int argc, char *argv[])
{
    int fd = -1;
    int ret = 0;
    char *dev_name = NULL;
    char *p_write_data = NULL;
    unsigned int arg1 = 0;
    unsigned int arg2 = 0;

    char readbuf[BUFSIZE] = {0};
    char writebuf[BUFSIZE] = {0};

    unsigned int off_end = 0; // 定义读写偏移位置
    unsigned int off_cur = 0; // 定义读写偏移位置
    unsigned int off = 0; // 定义读写偏移位置

    printf("*** Build Time: %s %s,Git Version: %s Git Remote: %s***\n", 
            __DATE__, __TIME__, GIT_VERSION, GIT_PATH);
    /** 
     * 获取传入参数并打印 
     * ./xxx.out /dev/sdevice x x x
     */
    arg_info_print(argc, argv);
    if (argc < 3) 
    {
        usage_info();
        return -1;
    }

    /** 解析参数 */
    dev_name = argv[1];          // 设备节点名称
    arg1 = atoi(argv[2]);  // 1=读，2=写，3=ioctl

    if(argv[3] != NULL && (arg1 == 1 || arg1 == 2))
    {
        arg2 = atoi(argv[3]);  // 读写的时候有效，表示读写的位置
    }
    if(argv[4] != NULL && (arg1 == 2))
    {
        p_write_data = argv[4];     // 写的时候有效，表示写的数据
    }

    /** 打开设备文件 */
    fd = open(dev_name,  O_RDWR); // 使用阻塞标志
    if (fd < 0)
    {
        PRTE("can't open file %s !\n", dev_name);
        return -1;
    }
    off_end = lseek(fd, 0, SEEK_END); // 读取字符设备文件可读写最大的大小

    if (arg1 == 1) /** 从设备节点指定位置读取数据 */
    {
        off = arg2;// 获取读写的偏移位置
        if(off > off_end)
        {
            off = off_end;
        }
        /* 从驱动文件读取数据 */
        ret = lseek(fd, off, SEEK_SET); // 将偏移量设置为距离起始地址 off的位置
        if (ret < 0)
        {
            PRTE("lseek %s %d failed!\n", dev_name, off);
            return -1;
        }
        #if 1
        struct pollfd fds[1] = {0};
        fds[0].fd = fd;
        fds[0].events = POLLIN;
        while (1)
        {
            ret = poll(fds, 1, 3000); // 3s超时
            if (ret == 0) // 超时
            {
                PRTE("poll time out 3s!!!\n");
            }
            else if (ret < 0) // 错误
            {
                PRTE("poll error!ret=%d\n", ret);
            }
            else // 有数据
            {
                if (fds[0].revents == POLLIN)
                {
                    ret = lseek(fd, off, SEEK_SET); // 将偏移量设置为距离起始地址 off的位置
                    if (ret < 0)
                    {
                        PRTE("lseek %s %d failed!\n", dev_name, off);
                        return -1;
                    }
                    memset(readbuf, 0, sizeof(readbuf));
                    ret = read(fd, readbuf, sizeof(readbuf));
                    if (ret < 0)
                    {
                        PRTE("read file %s failed!\n", dev_name);
                    }
                    else
                    {
                        off_cur = lseek(fd, 0, SEEK_CUR); // 读取当前位置的偏移量
                        /*  读取成功，打印出读取成功的数据 */
                        PRT("read data \"%s\" from %s! off=%d off_cur=%d\n", readbuf, dev_name, off, off_cur);
                    }
                }
            }
        }
        #else
        fd_set readfds = {0};
        struct timeval timeout = {0};
        while(1)
        {
            FD_ZERO(&readfds);
            FD_SET(fd, &readfds);
            /* 构造超时时间 */
            timeout.tv_sec = 3;           /* 3s */
            timeout.tv_usec = 500 * 1000; /* 500ms */
            ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
            switch (ret) 
            {
                case 0: /* 超时 */
                    PRTE("select time out!!!\n");
                    break;
                case -1:/* 错误 */
                    PRTE("select error!!!ret=%d\n", ret);
                    break;
                default:
                    if (FD_ISSET(fd, &readfds))
                    {
                        ret = lseek(fd, off, SEEK_SET); // 将偏移量设置为距离起始地址 off的位置
                        if (ret < 0)
                        {
                            PRTE("lseek %s %d failed!\n", dev_name, off);
                            return -1;
                        }
                        memset(readbuf, 0, sizeof(readbuf));
                        ret = read(fd, readbuf, sizeof(readbuf));
                        if (ret < 0)
                        {
                            PRTE("read file %s failed!\n", dev_name);
                        }
                        else
                        {
                            off_cur = lseek(fd, 0, SEEK_CUR); // 读取当前位置的偏移量
                            /*  读取成功，打印出读取成功的数据 */
                            PRT("read data \"%s\" from %s! off=%d off_cur=%d\n", readbuf, dev_name, off, off_cur);
                        }
                    }
                    break;
            }
        }
        #endif
    }
    else if (arg1 == 2) /** 从设备节点指定位置写入数据 */
    {
        off = arg2;// 获取读写的偏移位置
        if(off > off_end)
        {
            off = off_end;
        }
        
        if(p_write_data == NULL)
        {
            PRTE("The written data is missing!\n");
            return -1;
        }
        snprintf (writebuf, sizeof(writebuf), "%s", p_write_data);
        ret = lseek(fd, off, SEEK_SET); // 将偏移量设置为距离起始地址 off的位置
        if (ret < 0)
        {
            PRTE("lseek %s %d failed!\n", dev_name, off);
            return -1;
        }

        ret = write(fd, writebuf, strlen(p_write_data));
        if (ret < 0)
        {
            PRTE("write file %s failed!\n", dev_name);
            return -1;
        }
        off_cur = lseek(fd, 0, SEEK_CUR); // 读取当前位置的偏移量
        //PRT("write \"%s\" to %s success!off=%d off_cur=%d\n", writebuf, dev_name, off, off_cur);
    }
    else if (arg1 == 3) /** ioctl测试 */
    {
        int val = 0;
        struct __CMD_TEST cmd_test3 = {2, 3, 5};
        ioctl(fd,CMD_TEST0);
        ioctl(fd,CMD_TEST1, 1);
        ioctl(fd,CMD_TEST2, &val);
        printf("val = %d\n", val);

        ioctl(fd,CMD_TEST3, &cmd_test3);
    }

    /* 关闭设备 */
    ret = close(fd);
    if (ret < 0)
    {
        PRTE("can't close file %s !\n", dev_name);
        return -1;
    }

    return 0;
}

/**
 * @brief  sys_pts_get_ms
 * @note   获取系统时间戳 单位 ms
 * @param [in]
 * @param [out]
 * @retval 
 */
int sys_pts_get_ms(void)
{
    struct timespec tv = {0};
    long long lasttime = 0;

    clock_gettime(CLOCK_MONOTONIC, &tv);
    lasttime = tv.tv_sec * 1000 + tv.tv_nsec / (1000 * 1000);

    return lasttime;

}